package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.api.client.course.CatalogueClient;
import com.tianji.api.client.course.CategoryClient;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CataSimpleInfoDTO;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.course.CourseSimpleInfoDTO;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.domain.query.PageQuery;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.dto.LearningPlanDTO;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.domain.vo.LearningLessonVO;
import com.tianji.learning.domain.vo.LearningPlanPageVO;
import com.tianji.learning.domain.vo.LearningPlanVO;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.PlanStatus;
import com.tianji.learning.mapper.LearningLessonMapper;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.learning.service.ILearningRecordService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <p>
 * 学生课程表 服务实现类
 * </p>
 *
 * @author Yandel
 * @since 2025-04-21
 */
@Service
@RequiredArgsConstructor
public class LearningLessonServiceImpl extends ServiceImpl<LearningLessonMapper, LearningLesson> implements ILearningLessonService {

    private final CourseClient courseClient;

    private final CatalogueClient catalogueClient;


    private final LearningRecordMapper recordMapper;

    /**
     * 批量新增用户课程表
     * @param userId
     * @param courseIds
     */
    @Override
    @Transactional
    public void addUserLesson(Long userId, List<Long> courseIds) {
/**
 * 第一种写法，好理解
 */
//        //1、通过feign 调用课程服务，查询课程信息
        List<CourseSimpleInfoDTO> simpleInfoList = courseClient.getSimpleInfoList(courseIds);
        //2、封装po实体类，填充过期时间
        List<LearningLesson> list = new ArrayList<>();
        for (CourseSimpleInfoDTO cinfo : simpleInfoList) {
            LearningLesson lesson = new LearningLesson();//Po对象，封装课程信息
            lesson.setUserId(userId);
            lesson.setCourseId(cinfo.getId());

            Integer validDuration = cinfo.getValidDuration();
            if (validDuration != null) {
                LocalDateTime now = LocalDateTime.now();
//                lesson.setCreateTime(now);
                lesson.setExpireTime(now.plusMonths(validDuration));  //设置课程过期时间
            }

            list.add(lesson);
        }

/**
 * 第二种写法，使用stream流
 */
//        courseClient.getSimpleInfoList(courseIds).stream().forEach(courseSimpleInfoDTO -> {
//            LearningLesson lesson = new LearningLesson();
//            lesson.setUserId(userId);
//            lesson.setCourseId(courseSimpleInfoDTO.getId());
//
//            Integer validDuration = courseSimpleInfoDTO.getValidDuration();
//            if (validDuration != null) {
//                LocalDateTime now = LocalDateTime.now();
////                lesson.setCreateTime(now);
//                lesson.setExpireTime(now.plusMonths(validDuration));  //设置课程过期时间
//            }
//        });




        //3、批量保存
        //自己本身在service层，直接使用this调用
        this.saveBatch(list);
    }

    /**
     * 分页查询我的课程表
     * @param query
     * @return
     */
    @Override
    public PageDTO<LearningLessonVO> queryMyLessons(PageQuery query) {
        //1、获取当前人的信息    【检查请求头，请求头携带用户id】
        Long userId = UserContext.getUser();
        if (userId == null){
            throw  new BadRequestException("请先登录");
        }
        //2、分页查询我的课表
        Page<LearningLesson> page = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .page(query.toMpPage("latest_learn_time", false));

        //查询的结果列表
        List<LearningLesson> records = page.getRecords();
        if (CollUtils.isEmpty(records)){
            return PageDTO.empty(page);
        }

        /**
         * 获取课程ids
         */
        //3、远程调用课程服务，给vo中的 课程名称，课程封面，课程章节数量 字段
        Set<Long> CourseIds = records.stream().map(LearningLesson::getCourseId).collect(Collectors.toSet());

        //根据课程id集合查询课程简单信息
        List<CourseSimpleInfoDTO> simpleInfoList = courseClient.getSimpleInfoList(CourseIds);
        if (CollUtils.isEmpty(simpleInfoList)){
            throw new BizIllegalException("课程不存在");
        }

        /**
         * 这种写法避免下面双重for循环判断课程id来获取课程对象
         */
        //将simpleInfoList，课程集合转换成map结构 <课程id,课程对象> c 是 CourseSimpleInfoDTO对象
        Map<Long, CourseSimpleInfoDTO>  infoDTOMap = simpleInfoList.stream()
                .collect(Collectors.toMap(CourseSimpleInfoDTO::getId, c -> c));


        //4、将po中的数据封装到vo中
        List<LearningLessonVO> voList = new ArrayList<>();
        for (LearningLesson record : records) {
//            LearningLessonVO vo = new LearningLessonVO();
//            BeanUtils.copyProperties(record, vo); //拷贝属性  这是一般写法
            LearningLessonVO vo = BeanUtils.copyBean(record, LearningLessonVO.class);//这是该项目自定义封装了BeanUtils
            CourseSimpleInfoDTO infoDTO = infoDTOMap.get(record.getCourseId());//直接通过课程id获取课程对象
            if (infoDTO != null){
                vo.setCourseName(infoDTO.getName());
                vo.setCourseCoverUrl(infoDTO.getCoverUrl());
                vo.setSections(infoDTO.getSectionNum());
            }
            voList.add(vo);
        }

        //5、返回分页数据
        return PageDTO.of(page, voList);
    }

    /**
     * 查询当前正在学习的课程
     * @return
     */
    @Override
    public LearningLessonVO queryMyCurrentLesson() {
        //1.获取当期那登录用户ID
        Long userId = UserContext.getUser();

        //2、查询当前用户最近学习课程  按latest_learn_time排序，取第一条  正在学习中的 status -1
        LearningLesson lesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getStatus, LessonStatus.LEARNING)
                .orderByDesc(LearningLesson::getLatestLearnTime)
                .last("limit 1")
                .one();
        if (lesson == null){
            return null;
        }

        //3.远程调用课程服务，给vo中的 课程名称，课程封面，课程章节数量 字段
        CourseFullInfoDTO cinfo = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
        if (cinfo == null) {
            throw new BizIllegalException("课程不存在");
        }
        //4.查询当前用户课表中  已报名的 总的课程数
        Integer count = this.lambdaQuery().eq(LearningLesson::getUserId, userId).count();

        //5.通过feign远程调用课程服务  获取小节名称   和小节编号
        Long latestSectionId = lesson.getLatestSectionId();

        //一般写法
//        List<Long> sIds = new ArrayList<>();
//        sIds.add(latestSectionId);
//        List<CataSimpleInfoDTO> simpleInfoDTOList = catalogueClient.batchQueryCatalogue(sIds);
        //使用工具类
        List<CataSimpleInfoDTO> dtos = catalogueClient.batchQueryCatalogue(CollUtils.singletonList(latestSectionId));
        if (CollUtils.isEmpty(dtos)){
            throw new BizIllegalException("小节不存在");
        }

        //6.封装到返回vo
        LearningLessonVO vo = BeanUtils.copyBean(lesson, LearningLessonVO.class);
        vo.setCourseName(cinfo.getName());
        vo.setCourseCoverUrl(cinfo.getCoverUrl());
        vo.setSections(cinfo.getSectionNum());

        vo.setCourseAmount(count);//当期用户能学习课程总数

        CataSimpleInfoDTO cataSimpleInfoDTO = dtos.get(0);
        vo.setLatestSectionName(cataSimpleInfoDTO.getName());//最新学习小节名称
        vo.setLatestSectionIndex(cataSimpleInfoDTO.getCIndex());//最新学习小节编号

        return vo;
    }

    /**
     * 根据id查询指定课程的学习状态
     * @param courseId
     * @return
     */
    @Override
    public LearningLessonVO queryLearningStatus(Long courseId) {
        //1.获取当期那登录用户ID
        Long userId = UserContext.getUser();

        //2、查出当前用户有什么课程 SELECT * FROM learning_lesson WHERE user_id=2 and course_id=2
        //方式一：
//        LearningLesson learningLesson = this.lambdaQuery()
//                .eq(LearningLesson::getUserId, userId)
//                .eq(LearningLesson::getCourseId, courseId)
//                .one();

        //方式二：提出公共方法
        LambdaQueryWrapper<LearningLesson> lessonLambdaQueryWrapper = buildUserIdAndCourseIdWrapper(userId, courseId);
        LearningLesson learningLesson = getOne(lessonLambdaQueryWrapper);
        if (learningLesson == null){
            return null;
        }
        //3、封装vo
        // MP查询的Sql：SELECT id,user_id,course_id,status,week_freq,plan_status,
        // learned_sections,latest_section_id,latest_learn_time,create_time,
        // expire_time,update_time FROM learning_lesson WHERE (user_id = ? AND course_id = ?)
        return BeanUtils.copyBean(learningLesson, LearningLessonVO.class);
    }

    /**
     * 根据id删除指定课程
     * @param userId
     * @param courseId
     */
    @Override
    public void deleteLearningLesson( Long userId, Long  courseId) {
        //1.获取当前登录用户ID
        if (userId == null) {
            userId = UserContext.getUser();
        }

        //2、根据课程id删除课程
        //方式一：
//        this.lambdaQuery()
//                .eq(LearningLesson::getUserId, userId)
//                .eq(LearningLesson::getCourseId, courseId);
        //方式二：提出公共方法
        LambdaQueryWrapper<LearningLesson> wrapper = buildUserIdAndCourseIdWrapper(userId, courseId);
        //删除课程 DELETE FROM learning_lesson WHERE (user_id = ? AND course_id = ?)
        boolean remove = remove(wrapper);
        if (!remove){
            throw new BizIllegalException("删除失败");
        }
    }


    /**
     * 校验指定课程是否是课表中的有效课程
     * @param courseId
     * @return
     */
    @Override
    public Long isLessonValid(Long courseId) {
        //1、获取当前用户登录id
        Long userId = UserContext.getUser();
        if (userId == null){
            return null;
        }
        //2、查询该课程是否存在和其有效状态
        LambdaQueryWrapper<LearningLesson> queryWrapper = buildUserIdAndCourseIdWrapper(userId, courseId);
        LearningLesson lesson = getOne(queryWrapper);
        if (lesson == null){
            return null;
        }
        //如果该用户存在该课程，则返回该课程的id，则标明该课程有效
        //SELECT id,user_id,course_id,status,week_freq,plan_status,
        // learned_sections,latest_section_id,latest_learn_time,create_time,
        // expire_time,update_time FROM learning_lesson WHERE (user_id = ? AND course_id = ?)
        return lesson.getId();
    }

    @Override
    public Integer countLearningLessonByCourse(Long courseId) {
        //SELECT COUNT( * ) FROM learning_lesson WHERE (course_id = ? AND status IN (?,?,?))
        return this.lambdaQuery()
                .eq(LearningLesson::getCourseId, courseId)
                .in(LearningLesson::getStatus,
                        LessonStatus.NOT_BEGIN,
                        LessonStatus.LEARNING,
                        LessonStatus.FINISHED
                ).count();
    }

    /**
     * 提出来公共方法 -> 查询条件
     * @param userId 用户id
     * @param courseId 课程id
     * @return
     */
    private static LambdaQueryWrapper<LearningLesson> buildUserIdAndCourseIdWrapper(Long userId, Long courseId) {
        return new QueryWrapper<LearningLesson>()
                .lambda()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId);
    }

    @Override
    public void createLearningPlan(LearningPlanDTO dto) {
        //获取当前用户ID
        Long userId = UserContext.getUser();

        //查询learning_lesson 课表 条件user_id course_id
        LearningLesson learningLesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, dto.getCourseId())
                .one();
        if (learningLesson == null){
            throw new BizIllegalException("该课程没有加入到课表");
        }
        //修改课表

        // 这种方式，会覆盖其他字段，比如status，
        // plan_status，week_freq，latest_section_id，latest_learn_time，create_time，expire_time，update_time
        learningLesson.setWeekFreq(dto.getFreq());
//        boolean result = this.updateById(learningLesson);
//        if (!result){
//            throw new BizIllegalException("创建学习计划失败");
//        }

        //使用链式编程
        boolean result = this.lambdaUpdate()
                .set(LearningLesson::getWeekFreq, dto.getFreq())
                .set(LearningLesson::getPlanStatus, PlanStatus.PLAN_RUNNING)
                .eq(LearningLesson::getUserId, userId)
                .update();
        if (!result){
            throw new BizIllegalException("创建学习计划失败");
        }
    }


    @Override
    public LearningPlanPageVO queryMyPlans(PageQuery query) {
        //1.获取当前用户ID
        Long userId = UserContext.getUser();

        //2. TODO 查询积分



        //3.查询本周已学习的计划总数据
        //条件 userId  status in(0,1)   plan_status=1  查询 sum(week_freq)
        QueryWrapper<LearningLesson> wrapper = new QueryWrapper<>();
        wrapper.select("sum(week_freq) as plansTotal");
        wrapper.eq("user_id", userId);
        wrapper.in("status", LessonStatus.NOT_BEGIN, LessonStatus.LEARNING);
        wrapper.eq("plan_status", PlanStatus.PLAN_RUNNING);

        Map<String, Object> map = this.getMap(wrapper);
        //{plansTotal:7}
        Integer plansTotal = 0;
        if (map!=null && map.get("plansTotal")!=null){
             plansTotal = Integer.valueOf(map.get("plansTotal").toString());
        }

        //4.查询本周  实际学习小节数
//        SELECT COUNT(*)
//        from learning_record
//        WHERE user_id=2
//        AND finished=1
//        AND finish_time BETWEEN '2022-10-30 00:00:01' AND '2022-10-31 23:59:59'
        LocalDate now = LocalDate.now();
        LocalDateTime weekBeginTime = DateUtils.getWeekBeginTime(now);//本周开始时间
        LocalDateTime weekEndTime = DateUtils.getWeekEndTime(now);//本周结束时间
        Integer weekFinishedNum = recordMapper.selectCount(Wrappers.<LearningRecord>lambdaQuery()
                .eq(LearningRecord::getUserId, userId)
                .eq(LearningRecord::getFinished, true)
                .between(LearningRecord::getFinishTime, weekBeginTime, weekEndTime)
        );


        //5.查询课表数据
        Page<LearningLesson> page = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .in(LearningLesson::getStatus, LessonStatus.NOT_BEGIN, LessonStatus.LEARNING)
                .eq(LearningLesson::getPlanStatus, PlanStatus.PLAN_RUNNING)
                .page(query.toMpPage("latest_learn_time", false));

        List<LearningLesson> records = page.getRecords();
        if (CollUtils.isEmpty(records)){
            LearningPlanPageVO vo =   new LearningPlanPageVO();
            vo.setTotal(0L);
            vo.setPages(0L);
            vo.setList(CollUtils.emptyList());
            return vo;
        }

        //6、远程调用课程服务，获取课程信息
        Set<Long> courseIds = records.stream()
                .map(LearningLesson::getCourseId)
                .collect(Collectors.toSet());
        //查询所有的课程列表
        List<CourseSimpleInfoDTO> cinfo = courseClient.getSimpleInfoList(courseIds);
        if (CollUtils.isEmpty(cinfo)){
            throw new BizIllegalException("课程不存在");
        }
        /**
         * 将cinfo  list转换成map  <课程id,  CourseSimpleInfoDTO>
         */
        Map<Long, CourseSimpleInfoDTO> cinfoMap = cinfo.stream()
                .collect(Collectors.toMap(CourseSimpleInfoDTO::getId, c -> c));

        //7、查询学习记录表 本周  当前用户下 每一门课下 已学习的小结数量
//        SELECT lesson_id,COUNT(*) FROM learning_record
//        WHERE user_id=2
//        AND finished=1
//        AND finish_time BETWEEN '2022-10-30 00:00:01' AND '2022-10-31 23:59:59'
//        GROUP BY lesson_id
        QueryWrapper<LearningRecord> rWrapper = new QueryWrapper<>();
        rWrapper.select("lesson_id,COUNT(*) as finishedNum")
                .eq("user_id", userId)
                .eq("finished", true)
                .between("finish_time", weekBeginTime, weekEndTime)
                .groupBy("lesson_id");

        List<LearningRecord> learningRecords = recordMapper.selectList(rWrapper);
        //转乘map对象
        // key 是lessonId     value 是当前用户对该课程下的已学习小节数量
        //TODO 有bug 莫名其妙的没了
        Map<Long, Long>  courseWeekFinishedNum = learningRecords.stream()
                .collect(Collectors.toMap(LearningRecord::getLessonId, c -> c.getUserId()));


        //8、封装VO返回
        LearningPlanPageVO vo = new LearningPlanPageVO();
        vo.setWeekTotalPlan(plansTotal);
        vo.setWeekFinished(weekFinishedNum);
        //返回list
        List<LearningPlanVO> volist = new ArrayList<>();

        for (LearningLesson record : records) {
            LearningPlanVO planVO = BeanUtils.copyBean(record, LearningPlanVO.class);
            //课程信息      通过上面的cinfo 对象获取
            CourseSimpleInfoDTO infoDTO = cinfoMap.get(record.getCourseId());
            if (infoDTO != null){
                planVO.setCourseName(infoDTO.getName());//课程名称
                planVO.setSections(infoDTO.getSectionNum());//课程下的总小节数
            }
            Long num = courseWeekFinishedNum.get(record.getId());
            if (num != null) {
                planVO.setWeekLearnedSections(num.intValue());//本周已学习小节数
            }else{
                planVO.setWeekLearnedSections(0);
            }

            volist.add(planVO);
        }

        //Page返回方式一
//        vo.setList(volist);
//        vo.setTotal(page.getTotal());
//        vo.setPages(page.getPages());
//        return vo;

        //Page返回方式二
        return vo.pageInfo(page.getTotal(),page.getPages(),volist);
    }
}
